package bancosys.tec.security;

import java.security.Principal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.security.auth.Subject;

import bancosys.tec.security.authorization.Permission;

/**
 * @created 24/03/2008
 * @author Gustavo Almeida
 */
public class SecurityService {

    private static final String UNKNOWN_USERNAME = "Sistema";

    private Long systemId;

    private List<Long> frameworkIds = new ArrayList<Long>();

    private ThreadLocal<Subject> threadLocalSubject = new ThreadLocal<Subject>();

    /**
     * Associa um subject à thread atual.
     * 
     * @param subject sibject.
     */
    public void setCurrentThreadSubject(Subject subject) {
        this.threadLocalSubject.set(subject);
    }

    /**
     * Devolve o subject associado à thread atual.
     * 
     * @return o subject associado à thread atual.
     */
    public Subject getCurrentThreadSubject() {
        return this.threadLocalSubject.get();
    }

    /**
     * Devolve o nome do usuário atual da thread.
     * 
     * @return nome do usuário atual.
     */
    public String getCurrentUser() {
        Subject subject = this.getCurrentThreadSubject();
        String username = UNKNOWN_USERNAME;
        if (subject != null) {
            Principal principal = subject.getPrincipals().iterator().next();
            username = principal.getName();
        }
        return username;
    }

    /**
     * Verifica se o subject associado à thread atual possui determinada permissão.
     * 
     * @param type tipo da permissão
     * @param resourceId id da permissão.
     * @return <code>true</code> se o subject possuir a permissão, <code>false</code> caso contrário.
     */
    public boolean hasPermission(Class<? extends Permission> type, String resourceId) {
        return this.getPermission(type, resourceId) != null;
    }

    /**
     * Verifica se o subject possui determinada permissão.
     * 
     * @param subject subject
     * @param type tipo da permissão
     * @param resourceId id da permissão.
     * @return <code>true</code> se o subject possuir a permissão, <code>false</code> caso contrário.
     */
    public boolean hasPermission(Subject subject, Class<? extends Permission> type, String resourceId) {
        return this.getPermission(subject, type, resourceId) != null;
    }

    /**
     * Devolve a permissão solicitada do usuário associado à thread atual.
     * 
     * @param <T> tipo da permissão
     * @param type tipo da permissão
     * @param resourceId id da permissão.
     * @return devolve a permissão ou <code>null</code> caso ela não existir.
     */
    public <T extends Permission> T getPermission(Class<T> type, String resourceId) {
        Subject subject = this.getCurrentThreadSubject();
        return this.getPermission(subject, type, resourceId);
    }

    /**
     * Devolve a permissão solicitada.
     * 
     * @param <T> tipo da permissão
     * @param subject usuário.
     * @param type tipo da permissão
     * @param resourceId id da permissão.
     * @return devolve a permissão ou <code>null</code> caso ela não existir.
     */
    public <T extends Permission> T getPermission(Subject subject, Class<T> type, String resourceId) {
        if (subject == null) {
            return null;
        }
        for (Object credential : subject.getPublicCredentials()) {
            if (type.isInstance(credential)) {
                T permission = type.cast(credential);
                if (permission.match(type, resourceId)) {
                    return permission;
                }
            }
        }
        return null;
    }

    /**
     * Devolve as permissões solicitadas do usuário associado à thread atual.
     * 
     * @param <T> tipo da permissão
     * @param type tipo da permissão
     * @return devolve a lista de permissões
     */
    public <T extends Permission> Set<T> getPermissions(Class<T> type) {
        Subject subject = this.getCurrentThreadSubject();
        return this.getPermissions(subject, type);
    }

    /**
     * Devolve as permissões solicitadas.
     * 
     * @param <T> tipo da permissão
     * @param subject usuário.
     * @param type tipo da permissão
     * @return devolve a lista de permissões
     */
    public <T extends Permission> Set<T> getPermissions(Subject subject, Class<T> type) {
        Set<T> permissions = new HashSet<T>();
        for (Object permission : subject.getPublicCredentials()) {
            if (type.isInstance(permission)) {
                permissions.add(type.cast(permission));
            }
        }
        return permissions;
    }

    /**
     * Define o id do sistema.
     * 
     * @param systemId O systemId a ser definido.
     */
    public void setSystemId(Long systemId) {
        this.systemId = systemId;
    }

    /**
     * Devolve o system id.
     * 
     * @return o system id.
     */
    public Long getSystemId() {
        return this.systemId;
    }

    /**
     * @return the frameworkIds
     */
    public List<Long> getFrameworkIds() {
        return this.frameworkIds;
    }

    /**
     * @param frameworkIds the frameworkIds to set
     */
    public void setFrameworkIds(List<Long> frameworkIds) {
        this.frameworkIds = frameworkIds;
    }

    /**
     * Verifica se o security client com o id dado está ativo.
     * 
     * @param id id
     * @return boolean
     */
    public boolean isSecurityClientActive(Long id) {
        return this.systemId.equals(id) || this.frameworkIds.contains(id);
    }
}
